/**
 * 在内置工具类型中，对 属性修饰工具类型（回顾：16_工具类型_2、属性修饰工具类型.ts） 的进阶主要分为：
 * 1、深层的属性修饰
 * 2、基于 已知属性的部分修饰，以及 基于属性类型的部分修饰
*/

/**
 * 1、深层属性修饰
 * 对属性修饰进行递归处理
 * 
 * 深层可选Partial：DeepPartial
 * 深层必须Required：DeepRequired
 * 深层只读Readonly：DeepReadonly
*/
//深层可选Partial
type DeepPartial<T extends object> = {
    [K in keyof T]+?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
//深层必须Required
type DeepRequired<T extends object> = {
    [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
};
//深层只读Readonly
type DeepReadonly<T extends object> = {
    readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

//使用 DeepPartial可选 与 不使用 DeepPartial可选，进行对比：
type Food = {
    foo: 'bar',
    nested: {
        nestedFoo: string;
        nestedBar: {
            nestedBarFoo: string;
        };
    };
}
type TestPart = DeepPartial<Food>
//使用 DeepPartial可选
let test1: TestPart = {
    foo: 'bar',
    nested: {
        nestedFoo: "hanxuming"
    }
}
//不使用 DeepPartial可选，会报错
let test2: Food = {
    foo: 'bar',
    nested: {
        nestedFoo: "hanxuming"
    }
}


/**
 * 1、深层属性修饰
 * NonNullable 一个从联合类型中剔除 null | undefined 的工具类型
 * 
 * 因为在没有在 tsconfig.json 中开启 strictNullChecks 检查的情况下，
 * null 与 undefined会被视作其他类型的子类型，
 * 比如 string 类型会被认为包含了 null 与 undefined 类型
 * 所以，DeepNonNullable 需要在 tsconfig.json 中开启 strictNullChecks 检查的情况下才能正常工作。
 * 
 * 实现一个 DeepNonNullable 来递归剔除所有属性的 null 与 undefined
*/
type hasNull = string | number | null
//NonNullable 一个从联合类型中剔除 null | undefined 的工具类型
type NonNullable<T> = T extends null | undefined ? never : T;
// noNull：string | number
type noNull = NonNullable<hasNull>
//实现一个 DeepNonNullable 来递归剔除所有属性的 null 与 undefined：
type DeepNonNullable<T extends object> = {
    [K in keyof T]: T[K] extends object
    ? DeepNonNullable<T[K]>
    : NonNullable<T[K]>;
};


/**
 * 2、基于已知属性进行部分修饰
 * 
 * 让一个对象的三个已知属性中某个成为可选的，思路：
 * 
 * 1、拆分对象结构：
 * 那只要把 这个对象 拆成 A、B 两个对象结构，分别由三个属性和其他属性组成。
 * 即 Pick 与 Omit，，参考 “16_工具类型_3、结构工具类型.ts”。
 * 
 * 2、递归属性修饰Partial：
 * 然后让对象 A 的属性全部变为可选的，
 * 
 * 3、交叉类型，组合两个对象类型：
 * 和另外一个对象 B 组合起来
 * 
 * Partial可选示例
*/

//Flatten，一个辅助的工具类型，对于交叉类型的结构，Flatten 能够将它展平为 单层 的对象结构
type Flatten<T> = { [K in keyof T]: T[K] };

//pick 与 Omit，参考 “16_工具类型_3、结构工具类型.ts”
//pick 采集：从类型定义的属性中，选取指定一组属性，返回一个新的类型定义。
//Omit 省略/剔除：以一个类型为基础支持剔除某些属性，然后返回一个新类型。
type MarkPropsAsOptional<
    T extends object,
    K extends keyof T = keyof T
    > = Flatten<Partial<Pick<T, K>> & Omit<T, K>>;

/*MarkPropsAsOptionalStruct 变成了：{
    bar?: number;
    foo: string;
    baz: boolean;
}
*/
type MarkPropsAsOptionalStruct = MarkPropsAsOptional<
    {
        foo: string;
        bar: number;
        baz: boolean;
    },
    'bar'
>;

/**
 * 2、基于已知属性进行部分修饰
 * 来实现其它类型的部分修饰
*/
type MarkPropsAsRequired<
    T extends object,
    K extends keyof T = keyof T
    > = Flatten<Omit<T, K> & Required<Pick<T, K>>>;

type MarkPropsAsReadonly<
    T extends object,
    K extends keyof T = keyof T
    > = Flatten<Omit<T, K> & Readonly<Pick<T, K>>>;

type MarkPropsAsNonNullable<
    T extends object,
    K extends keyof T = keyof T
    > = Flatten<Omit<T, K> & NonNullable<Pick<T, K>>>;

//export {}：解决“无法重新声明块范围变量”错误提示问题
export { }